#pragma once
#include <iostream>
#include <unistd.h>
#include "Socket.hpp"
#include <sys/select.h>
#include "InetAddr.hpp"
using namespace socket_ns;
// using namespace log_ns;

class SelectServer
{
    const static int gnum = sizeof(fd_set) * 8;
    const static int gdefaultfd = -1;

public:
    SelectServer(uint16_t port) : _port(port), _listensock(std::make_unique<TcpSocket>())
    {
        _listensock->BuildListenSocket(_port);
    }

    // 处理新连接
    void Accepter()
    {
        // 说明此时的套接字就是listen套接字

        // 调用InetAddr的无参构造
        InetAddr addr;
        // 我们叫做连接事件就绪，此时就可以做accept获取新连接操作，并获取新连接的套接字描述符
        int sockfd = _listensock->Accepter(&addr); // 会不会被阻塞？一定不会阻塞！因为连接已经就绪了
        if (sockfd > 0)
        {
            // 获取成功，获取套接字信息
            LOG(DEBUG, "get a new link, client info %s:%d\n", addr.Ip().c_str(), addr.Port());
            // TODO
            bool flag = false;
            for (int pos = 1; pos < gnum; pos++)
            {
                if (fd_array[pos] == gdefaultfd)
                {
                    flag = true;
                    fd_array[pos] = sockfd;
                    LOG(INFO, "add %d to fd_array success!\n", sockfd);
                    break;
                }
            }
            // 说明该文件描述符没有被添加到fd_array当中
            if (!flag)
            {
                // 说明文件描述符集fd_array已经满
                LOG(WARNING, "server is full!!\n");
                // 因此关闭该文件描述符并且关闭连接
                ::close(sockfd);
            }
        }
        else
        {
            return;
        }
    }
    // 处理普通套接字就绪的
    void HandlerIO(int i)
    {
        // 普通套接字描述符， 进行正常的读写
        char buffer[1024];
        ssize_t n = ::recv(fd_array[i], buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client say# " << buffer << std::endl;
            std::string content = "<html><body><h1>hello pupu</h1></body></html>";
            std::string echo_str = "HTTP/1.0 200 OK\r\n";
            echo_str += "Content-Type: text/html\r\n";
            echo_str += "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";
            echo_str += content;
            // echo_str += buffer;
            ::send(fd_array[i], echo_str.c_str(), echo_str.size(), 0); // 临时方案
        }
        else if (n == 0)
        {
            // 在recv的时候，对方把文件描述符关闭了
            LOG(INFO, "client quit...\n");
            // 服务器也应该关闭我的连接：
            ::close(fd_array[i]);
            // 让select不要再关心这个fd了：
            fd_array[i] = gdefaultfd; // 设置为缺省描述符
        }
        else
        {
            LOG(ERROR, "recv error...\n");
        }
    }

    // 此时，select监管的套接字描述符增多，一定会存在大量的fd就绪，可能是普通的sockfd，也有可能是listensockfd。
    void HandlerEvent(fd_set &rfds)
    {
        // 事件派发，在Loop函数中不断检测是否有事件就绪，就绪事件就会在这里被处理，再在下面分别处理不同的事件
        // （是要获取新连接，还是处理普通套接字的读写
        for (int i = 0; i < gnum; i++)
        {
            if (fd_array[i] == gdefaultfd)
                continue;
            // fd一定是合法的fd
            // 合法的fd不一定就绪，判断fd是否就绪，fd在rfds集合中
            if (FD_ISSET(fd_array[i], &rfds))
            {
                // 读事件就绪，是listensockfd（读事件就绪）还是sockfd？
                if (_listensock->Sockfd() == fd_array[i])
                {
                    Accepter();
                }
                else
                {
                    HandlerIO(i);
                }
            }
        }
    }

    void InitServer()
    {
        for (int i = 0; i < gnum; i++)
        {
            fd_array[i] = gdefaultfd;
        }
        // 默认直接添加listensock到数组中
        fd_array[0] = _listensock->Sockfd();
    }
    void Loop()
    {
        while (true)
        {
            // 不能直接调用Accepter去获取连接，因为需要先有准备好的连接（等 + 拷贝）
            // listensock和accept我们把他们也看做IO类的函数，只关心连接到来，等价于读事件就绪
            //  _listensock->Accepter();

            // 1、文件描述符及集进行初始化
            // 读文件描述符集：系统当中禁止我们直接进行位操作，系统提供了专有接口，也就是操作宏
            fd_set rfds;
            FD_ZERO(&rfds);

            int max_fd = gdefaultfd;
            // 2、合法的fd，添加到rfds集合中
            for (int i = 0; i < gnum; i++)
            {
                if (fd_array[i] == gdefaultfd)
                    continue;
                //>=0 的值才是合法的文件描述符
                FD_SET(fd_array[i], &rfds);

                // 2.1更新出最大的文件描述符的值
                if (max_fd < fd_array[i])
                {
                    max_fd = fd_array[i];
                }
            }

            struct timeval timeout = {30, 0};

            // FD_SET(_listensock->Sockfd(), &rfds);
            // 随时更新最大描述符值
            int n = ::select(max_fd + 1, &rfds, nullptr, nullptr, nullptr /*&timeout*/); // 临时
            switch (n)
            {
            case 0:
                // LOG(DEBUG, "time out, %d.%d\n", timeout.tv_sec, timeout.tv_usec);
                break;
            case -1:
                LOG(ERROR, "select error\n");
                break;
            default:
                // LOG(DEBUG, "time out, %d.%d\n", timeout.tv_sec, timeout.tv_usec);
                LOG(INFO, "haved event ready, n: %d\n", n);
                // 处理事件，事件就在rfds中
                HandlerEvent(rfds);
                PrintDebug();
                // sleep(3);

                break;
            }
        }
    }
    void PrintDebug()
    {
        std::cout << "fd list: ";
        for (int i = 0; i < gnum; i++)
        {
            if (fd_array[i] == gdefaultfd)
                continue;
            std::cout << fd_array[i] << " ";
        }
        std::cout << "\n";
    }
    ~SelectServer()
    {
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensock;

    // 1.select要正常工作，需要借助一个辅助数组，来保存所有合法fd.
    int fd_array[gnum];
};
